#!/usr/bin/env python
# pylint: disable-msg = C0301, C0302, F0401, R0914, R0912, R0915, W0105

"""FlatFile.py
This file contains a flat file database class for downloads.py"""

# Get settings
from settings import LOCATION, HTTPPATH

#### Begin Code ####
import os
import ConfigParser

# Get error messages
from errors import error, Warn, user_error,simple_error

# Check that the locations exist
try:
    os.listdir(LOCATION)
except OSError:
    simple_error(LOCATION + " does not exist! Please correct LOCATION in settings.py")

# Initiate warning db
WARNINGS = Warn()

## Flat file database class
class Database:

    ## Database format:
    """
    Site.cfg
    
    Totally_Random_Content/:
    Category.cfg  Descriptions/  Random_Type/  Another_Random_Type/ Previews/ Thumbnails/

    Totally_Random_Content/Descriptions:
    Random1.txt
    
    Totally_Random_Content/Previews:
    Random1.png
    
    Totally_Random_Content/Thumbnails:
    Random1.png
    
    Totally_Random_Content/Random_Type:
    Random1.blah  Type_Title.txt

    Totally_Random_Content/Another_Random_Type:
    Random1.blahblah  Type_Title.txt

    Another_Category/:
    ...
    """
    
    ## Site.cfg:
    """
    [Site]
    name: Site Name
    icon: Path to site icon, relative to Site.cfg

    [Home]
    default_view: grid OR rows
    grid_dimensions: Number of RowsxNumber of Columns
    max_rows: Number of Rows
    grid_template: Default Django template for grid view. This can be split onto multiple pages by using a line break followed by a space.
    row_template: Default Django template for row view. See above for formatting details.
    
    [Categories]
    default_view: grid OR rows
    categories: Category1, Category2, Category3
    grid_dimensions: Number of RowsxNumber of Columns
    max_rows: Number of Rows
    fallback_icon: Path to category fallback icon, relative to Site.cfg
    grid_template: Default Django template for grid view. This can be split onto multiple pages by using a line break followed by a space.
    row_template: Default Django template for row view. See above for formatting details.
    
    [Objects]
    fallback_icon: Path to object fallback icon, relative to Site.cfg
    template: Default Django template for object view. See Categories >> grid_template for details on formatting
    sort:   0 - according to the Object Order set in Site.cfg
            1 - in alphabetical order of the object's filename
            2 - in reverse alphabetical order of the object's filename
            3 - by modified time, newest last
            4 - by modified time, newest first
            5 - in alphabetical order of the object's title
            6 - in reverse alphabetical order of the object's title
    
    [Custom]
    custom_option1: Custom Value
    custom_option1_type: string/integer/float/list_string,list_integer,list_float(comma separated)
    """
    
    ## Category.cfg:
    """
    [Main]
    title: Category title goes here
    short_description: Short category description goes here
    long_description: Long category description goes here
    icon: Category icon, relative to Category.cfg
    
    [Display]
    previews: Directory to use for previews, used on object page
    thumbnails: Directory to use for thumbnails, used on category page
    grid_dimensions: Number of RowsxNumber of Columns
    max_rows: Number of Rows in row view

    grid_template: Django template for grid view. This can be split onto multiple pages by using a line break followed by a space.
    row_template: Django template for row view. See above for formatting details.
    
    [Objects]
    order: Order of objects, using object names. eg: Object1,Object2,Object3
    template: Django template for object view. See Categories >> grid_template for details on formatting
    fallback_icon: Path to object fallback icon, relative to Category.cfg
    
    [Types]
    order: Order of types, using directory names. eg: PDF,PNG,TXT
    
    [Custom]
    custom_option1: Custom Value
    custom_option1_type: string/integer/float/list(comma separated)
    """
    
    def __init__(self):
        pass
    
    def path_exists(self, path):
        """Checks if a path exists. Return True if it does"""
        return os.path.exists(path)
    
    def get_filename(self, directory, name):
        """This function gets the actual filename of a file in a given directory, regardless of extension or case.
        If the directory or file doe"""
        try:
            # For every file in the directory specified
            for afile in os.listdir(directory):
                if name == afile.split('.')[0] and afile[-1] != "~": # If the file we want is the same as this file and it is not a backup file
                    return afile # return it
            # If it hasn't been found, return None
            return None
        except OSError: # If the directory specified doesn't exist
            return None

    def get_site_info(self):
        """This function gets the site-wide settings from the DB"""
        # Open Site.cfg
        try:
            site_config = ConfigParser.ConfigParser()
            site_config.read(LOCATION + "Site.cfg")
        except ConfigParser.NoSectionError: # If it doesn't exist, call an error
            user_error("<i>%sSite.cfg</i> was not found!"%(LOCATION))
        except ConfigParser.ParsingError: # If there is an error with the file, call an error
            user_error("There was an error parsing <i>%sSite.cfg</i>. Please check through it carefully."%(LOCATION))
        
        try:
            # Get site name
            name = site_config.get('Site', 'name')
        except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
            name = "Downloads" # Default name to Downloads
            
        try:
            # Get site icon, relative to content folder
            icon = site_config.get('Site', 'icon')
        except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): # If there is none, warn the user
            WARNINGS.add_warning("There is no site-wide icon defined in <i>%sSite.cfg</i>! Please add one!"%(LOCATION), 1)
            icon = None
        # Check if icon exists
        if self.path_exists(LOCATION + str(icon)) == False:
            # Warn if it doesn't
            # Don't warn if done that already
            if icon != None:
                WARNINGS.add_warning("The site-wide icon defined in <i>%sSite.cfg</i> doesn't exist! Please correct this!"%(LOCATION), 1)
                icon = None
        else:
            # Add an http path for it
            icon = HTTPPATH + icon
        
            
        # Get homepage settings
        try:
            # Get default view for the homepage
            home_view = site_config.get('Home', 'default_view')
        except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
            WARNINGS.add_warning("""No default view set for homepage. Using grid mode. To change this, add this to <i>%sSite.cfg</i>:
<pre>[Home]
default_view: grid OR rows</pre>"""%(LOCATION), 4)
            home_view = "grid"
        
        try:
            # Get homepage grid dimensions
            home_grid_dimensions = site_config.get('Home', 'grid_dimensions').split('x')
            home_grid_dimensions = (int(home_grid_dimensions[0]), int(home_grid_dimensions[1]))
        except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
            WARNINGS.add_warning("""No homepage grid dimensions found. Using default of 2x3. To change this, add this to <i>%sSite.cfg</i>:
<pre>[Home]
grid_dimensions: Number of RowsxNumber of Columns</pre>"""%(LOCATION), 4)
            home_grid_dimensions = (2, 3) # Set fallback grid dimensions (Is there a better way to do this?)
        except ValueError:
            WARNINGS.add_warning("""Your homepage grid dimensions are not in a valid format. In <i>%sSite.cfg</i>:
<pre>[Home]
grid_dimensions: Number of RowsxNumber of Columns</pre>"""%(LOCATION), 1)
            home_grid_dimensions = (2, 3) # Set fallback grid dimensions (Is there a better way to do this?)
            
        try:
            # Get homepage row limit
            home_max_rows = int(site_config.get('Home', 'max_rows'))
        except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
            WARNINGS.add_warning("""No homepage row limit found. Using default of 10 rows. To change this, add this to <i>%sSite.cfg</i>:
<pre>[Home]
max_rows: Number of Rows</pre>"""%(LOCATION), 4)
            home_max_rows = 10 # Set fallback row limit (Tell me if there's a better way to do this!)
        except ValueError:
            WARNINGS.add_warning("""Your homepage row limit is not in a valid format. In <i>%sSite.cfg</i>:
<pre>[Home]
max_rows: Number of Rows</pre>"""%(LOCATION), 1)
            home_max_rows = 10 # Set fallback row limit (Is there a better way to do this?)
        
        try:
            # Get homepage grid template
            home_grid_template = site_config.get('Home', 'grid_template')
        except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
            WARNINGS.add_warning("""No grid view template for the homepage has been found. Using the template engine's default. To change, add this to <i>%sSite.cfg</i>:
<pre>[Home]
grid_template: Django template for grid view. This can be split onto multiple pages by using a line break followed by a space</pre>"""%(LOCATION), 4)
            home_grid_template = None # Set it to None so that the template engine can catch it

        try:
            # Get homepage row template
            home_row_template = site_config.get('Home', 'row_template')
        except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
            WARNINGS.add_warning("""No row view template for the homepage has been found. Using the template engine's default. To change, add this to <i>%sSite.cfg</i>:
<pre>[Home]
row_template: Django template for row view. This can be split onto multiple pages by using a line break followed by a space</pre>"""%(LOCATION), 4)
            home_row_template = None # Set it to None so that the template engine can catch it
            
        # Get default category settings
        try:
            # Get default category view
            category_view = site_config.get('Categories', 'default_view')
        except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
            WARNINGS.add_warning("""No default view mode found. Using homepage view as default. To change this, add this to <i>%sSite.cfg</i>:
<pre>[Categories]
default_view: grid OR rows</pre>"""%(LOCATION), 4)
            category_view = home_view # Use home view mode as a fallback
            
        try:
            # Get grid dimensions
            category_grid_dimensions = site_config.get('Categories', 'grid_dimensions').split('x')
            category_grid_dimensions = (int(category_grid_dimensions[0]), int(category_grid_dimensions[1]))
        except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
            WARNINGS.add_warning("""No default grid dimensions found. Using homepage dimensions as default. To change this, add this to <i>%sSite.cfg</i>:
<pre>[Categories]
grid_dimensions = Number of RowsxNumber of Columns</pre>"""%(LOCATION), 4)
            category_grid_dimensions = home_grid_dimensions # Use homepage defaults as a fallback
        except ValueError:
            WARNINGS.add_warning("""Your default category grid dimensions are not in a valid format. In <i>%sSite.cfg</i>:
<pre>[Categories]
grid_dimensions: Number of RowsxNumber of Columns</pre>"""%(LOCATION), 1)
            category_grid_dimensions = home_grid_dimensions
            
        try:
            # Get row limit
            category_max_rows = int(site_config.get('Categories', 'max_rows'))
        except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
            WARNINGS.add_warning("""No default row limit found. Using homepage row limit. To change this, add this to <i>%sSite.cfg</i>:
<pre>[Categories]
max_rows: Number of rows</pre>"""%(LOCATION), 4)
            category_max_rows = home_max_rows # Use homepage defaults as a fallback
        except ValueError:
            WARNINGS.add_warning("""Your default category row limit is not in a valid format. Change this in <i>%sSite.cfg</i>:
<pre>[Categories]
max_rows: Number of Rows</pre>"""%(LOCATION), 1)
            category_max_rows = home_max_rows # Set fallback row limit
            
        try:
            # Get fallback icon for categories
            category_fallback_icon = site_config.get('Categories', 'fallback_icon')
        except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): # If there is none, warn the user
            WARNINGS.add_warning("""There is no defined category fallback icon. To define one, add this to <i>%sSite.cfg</i>:
<pre>[Categories]
fallback_icon: Path to icon, relative to %s</pre>"""%(LOCATION, LOCATION), 3)
            category_fallback_icon = None # Set it to None to let it be caught later on
            
        # Check if icon exists
        if self.path_exists(LOCATION + str(category_fallback_icon)) == False:
            # Warn if it doesn't and you haven't warned already
            if category_fallback_icon != None:
                WARNINGS.add_warning("The fallback icon (%s%s) for categories defined in <i>%sSite.cfg</i> doesn't exist! Please correct this!"%(LOCATION, category_fallback_icon, LOCATION), 1)
                category_fallback_icon = None
        else:
            # Add an http path for it
            category_fallback_icon = HTTPPATH + category_fallback_icon
        
        if category_fallback_icon == None:
            # Use the site icon as a fallback
            category_fallback_icon = icon

        try:
            # Get category grid template
            category_grid_template = site_config.get('Categories', 'grid_template')
        except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
            # Set to blank so that it can be caught later
            category_grid_template = ""
        
        # Warn user if the category grid template is blank or nonexistant
        if category_grid_template == "":
            WARNINGS.add_warning("""No default category grid view template has been found. Using the template engine's default. To change, add this to <i>%sSite.cfg</i>:
<pre>[Categories]
grid_template: Django template for grid view. This can be split onto multiple pages by using a line break followed by a space</pre>"""%(LOCATION), 3)
            category_grid_template = None # Set it to None so that the template engine can catch it
        try:
            # Get category row template
            category_row_template = site_config.get('Categories', 'row_template')
        except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
            # Set to blank so that it can be caught later
            category_row_template = ""
        
        # Warn user if the category grid template is blank or nonexistant
        if category_row_template == "":
            # Set to blank so that it can be caught later
            WARNINGS.add_warning("""No default category row view template has been found. Using the template engine's default. To change, add this to <i>%sSite.cfg</i>:
<pre>[Categories]
row_template: Django template for row view. This can be split onto multiple pages by using a line break followed by a space</pre>"""%(LOCATION), 3)
            category_row_template = None # Set it to None so that the template engine can catch it
            
        try:
            # Get fallback icon for objects
            object_fallback_icon = site_config.get('Objects', 'fallback_icon')
        except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): # If there is none, warn the user
            WARNINGS.add_warning("""There is no defined object fallback icon. To define one, add this to <i>%sSite.cfg</i>:
<pre>[Objects]
fallback_icon: Path to icon, relative to %s</pre>"""%(LOCATION, LOCATION), 3)
            object_fallback_icon = None # Set it to None so that it can be caught later
        
        # Check if icon exists
        if self.path_exists(LOCATION + str(object_fallback_icon)) == False:
            # Warn if it doesn't and if you haven't warned already
            if object_fallback_icon != None:
                WARNINGS.add_warning("The fallback icon (%s%s) for objects defined in <i>%sSite.cfg</i> doesn't exist!"%(LOCATION, object_fallback_icon, LOCATION), 1)
                object_fallback_icon = None
        else:
            # Add an http path for it
            object_fallback_icon = HTTPPATH + object_fallback_icon
        if object_fallback_icon == None:
            # Use the category fallback icon as a fallback
            object_fallback_icon = category_fallback_icon
        
        try:
            # Get default sort order for objects
            object_sort_method = int(site_config.get('Objects', 'sort'))
        except (ConfigParser.NoOptionError, ConfigParser.NoSectionError, ValueError):
            WARNINGS.add_warning("""No default sort method has been specified for objects. To change this, add this to <i>%sSite.cfg</i>:
<pre>[Objects]
sort:   0 - according to the Object Order set in Site.cfg
        1 - in alphabetical order of the object's filename
        2 - in reverse alphabetical order of the object's filename
        3 - by modified time, newest last
        4 - by modified time, newest first
        5 - in alphabetical order of the object's title
        6 - in reverse alphabetical order of the object's title</pre>"""%(LOCATION), 4)
            object_sort_method = 5
        
        try:
            # Get object view template
            object_template = site_config.get('Objects', 'template')
        except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
            # Set to blank so that it can be caught later
            object_template = ""
        
        # Warn user if the category grid template is blank or nonexistant
        if object_template == "":
            WARNINGS.add_warning("""No default object view template has been found. Using the template engine's default. To change, add this to <i>%sSite.cfg</i>:
<pre>[Objects]
template: Django template code for object view. This can be split onto multiple pages using a line break followed by a space</pre>"""%(LOCATION), 3)
            object_template = None # Set it to None so that the template engine can catch it later
        
        try:
            # Get custom options
            # Get a list of options in the custom section
            options = site_config.options('Custom')
            # Get the value of each option and put it all into a dictionary
            # {'option':value}
            custom_options = {}
            for option in options:
                custom_option_value = site_config.get('Custom', option)
                # Get option type
                try:
                    option_type = site_config.get('Custom', option + "_type")
                except ConfigParser.NoOptionError:
                    option_type = "string"
                # Option types: string/integer/float/list
                if option_type == "integer":
                    custom_option_value = int(custom_option_value)
                elif option_type == "float":
                    custom_option_value = float(custom_option_value)
                elif option_type in ("list_string", "list_integer", "list_float"):
                    custom_option_value = option.split(',')
                    if option_type == "list_integer":
                        custom_option_value = [ int(opt) for opt in custom_option_value ]
                    elif option_type == "list_float":
                        custom_option_value = [ float(opt) for opt in custom_option_value ]
                custom_options[option] = custom_option_value
        except ConfigParser.NoSectionError:
            # Ignore the error if there is no custom section but create the dictionary so that there are no further errors
            custom_options = {}
        
        # Return a dictionary of the information we've collected
        return {'name':name,
                'icon':icon,
                'home_view':home_view,
                'home_grid_dimensions':home_grid_dimensions,
                'home_max_rows':home_max_rows,
                'home_grid_template':home_grid_template,
                'home_row_template':home_row_template,
                'category_view':category_view,
                'category_grid_dimensions':category_grid_dimensions,
                'category_max_rows':category_max_rows,
                'category_fallback_icon':category_fallback_icon,
                'category_grid_template':category_grid_template,
                'category_row_template':category_row_template,
                'object_sort_method':object_sort_method,
                'object_fallback_icon':object_fallback_icon,
                'object_template': object_template,
                'custom_options':custom_options}
    
    def get_categories(self):
        """This function gets a list of categories to be used in listing order"""
        
        # Open Site.cfg
        try:
            site_config = ConfigParser.ConfigParser()
            site_config.read(LOCATION + "Site.cfg")
            # If it doesn't exist, cause an error
        except ConfigParser.NoSectionError:
            user_error("<i>%sSite.cfg</i> was not found!"%(LOCATION))
        except ConfigParser.ParsingError: # If there is an error with the file, call an error
            user_error("There was an error parsing <i>%sSite.cfg</i>. Please check through it carefully."%(LOCATION))
        # Get a list of categories in the display order
        try:
            categories = site_config.get('Categories', 'categories').split(',')
        except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
            # Make it blank so that it can be caught later
            categories = []
        # Give an error if there are no categories
        if len(categories) == 0:
            # Warn the user
            user_error("""No categories have been listed. Add the following to <i>%sSite.cfg</i>:
<pre>[Categories]
categories: Category1,Category2,Category3</pre>"""%(LOCATION))
        # Then check that all the categories exist
        listed_categories = os.listdir(LOCATION)
        for category in categories:
            # If it doesn't exist, warn user and disable it
            if category not in listed_categories:
                Warning("%s does not exist in %s. It will be disabled."%(category, LOCATION))
                categories.remove(category)
        
        return categories
                
    def get_category(self, category):
        """This function will return details of a category such as it's Title, Description and Template when given it's name"""
        category_location = LOCATION + category + "/" 
        httpcategory_location = HTTPPATH + category + "/"
        category_config = ConfigParser.ConfigParser()
        # Check if Category.cfg exists
        if self.path_exists(category_location + "Category.cfg") == False:
            error("<i>%sCategory.cfg</i> does not exist. Please create it with the template below.\n<hr>"%(category_location) + """
<pre>[Main]
title: Category title goes here
short_description: Short category description goes here
long_description: Long category description goes here
icon: Category icon, relative to Category.cfg

[Display]
previews: Directory to use for previews, used on object page
thumbnails: Directory to use for thumbnails, used on category page
grid_dimensions: Number of RowsxNumber of Columns
max_rows: Number of Rows in row view

grid_template: Category grid view template, using django templating
row_template: Category row view template, using django templating

[Objects]
order: Order of objects, using object names. eg: Object1,Object2,Object3
sort:   0 - according to the Object Order set in Category.cfg
        1 - in alphabetical order of the object's filename
        2 - in reverse alphabetical order of the object's filename
        3 - by modified time, newest last
        4 - by modified time, newest first
        5 - in alphabetical order of the object's title
        6 - in reverse alphabetical order of the object's title
        
template: Object view template, using django templating
fallback_icon: Path to object fallback icon, relative to %s

[Types]
order: Order of types, using directory names. eg: PDF,PNG,TXT

[Custom]
custom_option1: Custom Value
custom_option1_type: string/integer/float/list(comma separated)</pre>
"""%(category_location))
            user_error('This category does not exist!')
            
        # Open Category.cfg
        try:
            category_config.read(category_location + "Category.cfg")
        # If it doesn't exist, cause an error
        except (ConfigParser.ParsingError,ConfigParser.MissingSectionHeaderError): # If there is an error with the file, call an error
            user_error("There was an error parsing <i>%sCategory.cfg</i>. Please check through it carefully."%(category_location))

        # Get all the category settings
        try:
            category_title = category_config.get('Main', 'title')
        except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
            # Warn and use category name as a fallback
            WARNINGS.add_warning("""%s category doesn't have a set title. Using category name as a fallback. To set a title, add this to the <i>%sCategory.cfg</i>:
<pre>[Main]
title: Title here</pre>"""%(category, category_location), 1)
            category_title = category
        try:
            category_short_description = category_config.get('Main', 'short_description')
        except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): # If no description is set, leave it blank
            # Warn the user
            WARNINGS.add_warning("""%s category does not have a short description. To set one, add this to the <i>%sCategory.cfg</i>:
<pre>[Main]
short_description: Short description of the category</pre>"""%(category, category_location), 3)
            category_short_description = ""
        try:
            category_long_description = category_config.get('Main', 'long_description')
        except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): # If no description is set, leave it blank
            # Warn the user
            WARNINGS.add_warning("""%s category does not have a long description. Using short description. To set one, add this to the <i>%sCategory.cfg</i>:
<pre>[Main]
long_description: long description of the category</pre>"""%(category, category_location), 3)
            category_long_description = category_short_description
        
        # Get preview directory
        try:
            category_previews = category_config.get('Display', 'previews') + "/"
        except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
            category_previews = ""
        # Check that previews directory exists and error if it doesn't, leave it blank
        if self.path_exists(category_location + category_previews) == False:
            WARNINGS.add_warning("""There is no preview directory for %s. To set your previews directory, add this to your <i>%sCategory.cfg</i>:
<pre>[Display]
previews: Preview directory path, relative to %s</pre>"""%(category, category_location, category_location), 2)
            category_previews = ""
        # Get thumbnails directory
        try:
            category_thumbnails = category_config.get('Display', 'thumbnails') + "/"
        except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
            category_thumbnails = ""
        # Check that thumbnails directory exists and error if it doesn't, use the preview directory
        if self.path_exists(category_thumbnails):
            WARNINGS.add_warning("There is no thumbnails directory for %s! Using the preview directory instead."%category, 2)
            category_thumbnails = category_previews
            
        # Check that grid dimensions are valid
        try:
            grid_dimensions = category_config.get('Display', 'grid_dimensions').split('x')
            grid_dimensions = (int(grid_dimensions[0]), int(grid_dimensions[1]))
        except IndexError:
            error("<i>%sCategory.cfg</i> has badly formatted grid dimensions. Must be RowsxColumns."%(category_location))
        except ValueError:
            error("<i>%sCategory.cfg</i> has an invalid number of rows or columns. Must be an integer."%(category_location))
        except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
            # Use the site default
            grid_dimensions = self.get_site_info()['category_grid_dimensions']
            
        # Check that row dimensions are valid
        try:
            max_rows = int(category_config.get('Display', 'max_rows'))
        except ValueError:
            error("<i>%sCategory.cfg</i> has an invalid max number of rows. Must be an integer."%(category_location))
        except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
            # Use the site default
            max_rows = grid_dimensions = self.get_site_info()['category_max_rows']
        
        try:
            # Get the object order
            objects_order = category_config.get('Objects', 'order').split(',')
        except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
            # Set it to None so that it can be caught later
            objects_order = None
        
        try:
            # Get sort order for objects
            object_sort_method = int(category_config.get('Objects', 'sort'))
        except (ConfigParser.NoOptionError, ConfigParser.NoSectionError, ValueError):
            WARNINGS.add_warning("""No sort method has been specified for objects in the %s category. To change this, add this to <i>%sCategory.cfg</i>:
<pre>[Objects]
sort:   0 - according to the Object Order
        1 - in alphabetical order of the object's filename
        2 - in reverse alphabetical order of the object's filename
        3 - by modified time, newest last
        4 - by modified time, newest first
        5 - in alphabetical order of the object's title
        6 - in reverse alphabetical order of the object's title</pre>"""%(category, category_location), 4)
            # Fallback to the default method in Site.cfg
            object_sort_method = self.get_site_info()['object_sort_method']
        
        try:
            # Get the object type order
            object_type_order = category_config.get('Types', 'order').split(',')
        except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
            # Have a blank list
            object_type_order = []
            
        try:
            # Get the category grid view template
            category_grid_template = category_config.get('Display', 'grid_template')
        except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
            # Set to blank so that it can be caught later
            category_grid_template = ""
            
        try:
            # Get the category row view template
            category_row_template = category_config.get('Display', 'row_template')
        except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
            # Set to blank so that it can be caught later
            category_row_template = ""
        try:
            # Set to blank so that it can be caught later
            object_template = category_config.get('Objects', 'template')
        except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
            # Set to blank so that it can be caught later
            object_template = ""
        
        # Check if the templates are blank. If so, use default templates
        if category_grid_template == "":
            WARNINGS.add_warning("""No grid view template has been found for the %s category. To change, add this to <i>%sCategory.cfg</i>:
<pre>[Display]
grid_template: Django template for grid view. This can be split onto multiple pages by using a line break followed by a space</pre>"""%(category, category_location), 3)
            category_grid_template = self.get_site_info()['category_grid_template']
        if category_row_template == "":
            WARNINGS.add_warning("""No row view template has been found for the %s category. To change, add this to <i>%sCategory.cfg</i>:
<pre>[Display]
row_template: Django template for rows view. This can be split onto multiple pages by using a line break followed by a space</pre>"""%(category, category_location), 3)
            category_row_template = self.get_site_info()['object_template']
        if object_template == "":
            WARNINGS.add_warning("""No object view template has been found for the %s category. To change, add this to <i>%sCategory.cfg</i>:
<pre>[Objects]
template: Django template for viewing objects. This can be split onto multiple pages by using a line break followed by a space</pre>"""%(category, category_location), 3)
            object_template = self.get_site_info()['object_template']
        
        try:
            # Get the category icon
            category_icon = category_config.get('Main', 'icon')
        except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): # If there is none, warn the user
            WARNINGS.add_warning("""There is no icon defined for the %s category! Please add this to <i>%sCategory.cfg</i>:
<pre>[Main]
icon: Path to icon, relative to %s</pre>"""%(category, category_location, category_location), 2)
            category_icon = None
            
        # Check if icon exists
        if self.path_exists(category_location + str(category_icon)) == False:
            WARNINGS.add_warning("The icon defined for the %s category does not exist. Please correct this in the <i>%sCategory.cfg</i> file!"%(category, category_location), 1)
            category_icon == None
        # If the icon doesn't exist
        if category_icon == None:
            # Use category fallback icon as a fallback
            category_icon = self.get_site_info()['category_fallback_icon']
        else:
            category_icon = httpcategory_location + category_icon
        
        try:
            # Get fallback icon for objects
            object_fallback_icon = category_config.get('Objects', 'fallback_icon')
        except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): # If there is none, warn the user
            WARNINGS.add_warning("""There is no defined object fallback icon for the %s category. To define one, add this to <i>%sCategory.cfg</i>:
<pre>[Objects]
fallback_icon: Path to icon, relative to %s</pre>"""%(category, category_location, category_location), 3)
            object_fallback_icon = None # Set it to None so that it can be caught later
        
        # Check if icon exists
        if self.path_exists(category_location + str(object_fallback_icon)) == False:
            # Warn if it doesn't and if you haven't warned already
            if object_fallback_icon != None:
                WARNINGS.add_warning("The fallback icon (%s%s) for objects in the %s category doesn't exist! Please correct this in the <i>%sCategory.cfg</i> file!"%(category_location, object_fallback_icon, category, category_location), 1)
                object_fallback_icon = None
        else:
            # Add an http path for it
            object_fallback_icon = httpcategory_location + object_fallback_icon
        if object_fallback_icon == None:
            # Use the site object fallback icon as a fallback
            object_fallback_icon = self.get_site_info()['object_fallback_icon']
            
        try:
            # Get custom options
            # Get a list of options in the custom section
            options = category_config.options('Custom')
            # Remove _type options
            for option in options:
                if "_type" in option:
                    options.remove(option)
            # Get the value of each option and put it all into a dictionary
            # {'option':value}
            custom_options = {}
            for option in options:
                custom_option_value = category_config.get('Custom', option)
                # Get option type
                try:
                    option_type = category_config.get('Custom', option + "_type")
                except ConfigParser.NoOptionError:
                    option_type = "string"
                # Option types: string/integer/float/list
                if option_type == "integer":
                    custom_option_value = int(custom_option_value)
                elif option_type == "float":
                    custom_option_value = float(custom_option_value)
                elif option_type in ("list_string", "list_integer", "list_float"):
                    custom_option_value = option.split(',')
                    if option_type == "list_integer":
                        custom_option_value = [ int(opt) for opt in custom_option_value ]
                    elif option_type == "list_float":
                        custom_option_value = [ float(opt) for opt in custom_option_value ]
                        
                custom_options[option] = custom_option_value
        except ConfigParser.NoSectionError:
            # Ignore the error if there is no custom section but create the dictionary so that there are no further errors
            custom_options = {}
            
        # Return a dictionary of this information
        return {'name': category,
                'title':category_title,
                'short_description':category_short_description,
                'long_description':category_long_description,
                'previews':category_previews,
                'thumbnails':category_thumbnails,
                'grid_dimensions':grid_dimensions,
                'max_rows':max_rows,
                'objects_order':objects_order,
                'object_sort_method':object_sort_method,
                'object_type_order':object_type_order,
                'grid_template':category_grid_template,
                'row_template':category_row_template,
                'object_template':object_template,
                'icon':category_icon,
                'object_fallback_icon':object_fallback_icon,
                'custom_options':custom_options}
                
    def get_category_type(self, category, category_type):
        """This function gets information about a particular file type in a category"""
        category_location = LOCATION + category + "/"
        ## Type.cfg
            
        """[Type]
        title: The name of the file type
        description: A short description of the file type, not essential
        """
        # Open Type.cfg
        try:
            type_config = ConfigParser.ConfigParser()
            type_config.read(category_location + category_type + "/Type.cfg")
            # Get the title
            try:
                title = type_config.get('Type', 'title')
            except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): # If it doesn't exist, set it to None so that it can be caught later
                # Warn first
                WARNINGS.add_warning("""Name of the %s file type is not set. Using it's directory name as a fallback. To set the type name, %s%s/Type.cfg must contain:
<pre>[Type]
title: The name of the file type</pre>"""%(category_type, category_location, category_type), 2)
                title = None
            except ConfigParser.ParsingError: # If there is an error with the file, call an error
                user_error("There was an error parsing %s. Please check through it carefully."%(category_location + category_type + "/Type.cfg"))
            # Get the title
            try:
                description = type_config.get('Type', 'description')
            except (ConfigParser.NoOptionError, ConfigParser.NoSectionError): # If it doesn't exist, set it to None so that it can be caught later
                # Warn the user
                WARNINGS.add_warning("""There is no description for the %s file type in the %s category. To set one, the %s%s/Type.cfg must contain:
<pre>[Type]
description: A description of the file type</pre>"""%(category_type, category, category_location, category_type), 3)
                description = None
        # If it doesn't exist, warn and use a fallback
        except ConfigParser.NoSectionError:
            WARNINGS.add_warning("%s%s/Type.cfg does not exist. Please create it with the template below.\n<hr>" + """
<pre>[Type]
title: The name of the file type
description: A short description of the file type, not essential</pre>"""%(category_location, category_type), 1)
            # Set the title, description to None so that it can be caught later
            title = None
            description = None
        if title == None or title == "":
            # Use the type's name as a fallback for its title
            title = category_type
        if description == None:
            # Use the title as it's description if none is present
            description = title
            
        # Return the title and description as a dictionary
        return {'name':category_type, 'title':title, 'description':description}
        
    def get_category_types(self, category):
        """This function gets the details for all the types in a category"""
        category_location = LOCATION + category + "/"
        # Get all the types for the category
        category_types = self.get_category(category)['object_type_order']
        existent_types = []
        # Check if types exist
        for category_type in category_types:
            if self.path_exists(category_location + category_type) == False: # If there is no folder with its name
                # Warn that it doesn't
                WARNINGS.add_warning("Type called %s was not found in the %s category. Fix the <i>%sCategory.cfg</i> file to remove this warning."%(category_type, category, category_location), 1)
                category_types.remove(category_type) # Remove it from the type list
        # Get details for each type
        for category_type in category_types:
            # and append it to the list of types
            existent_types.append(self.get_category_type(category, category_type))
        
        # Return the list of types
        return existent_types
    
    def get_objects(self, category, sort_type=0, fallback=5):
        """This function gets objects in a category sorted in a few different ways.
        If sort_type is:
                0 - according to the Object Order set in Category.cfg
                1 - in alphabetical order of the object's filename
                2 - in reverse alphabetical order of the object's filename
                3 - by modified time, newest last
                4 - by modified time, newest first
                5 - in alphabetical order of the object's title
                6 - in reverse alphabetical order of the object's title
        If the fallback variable is set, and it is only used with sort_type=0, that sort method
        will be used if sorting using the object order fails."""
        
        category_location = LOCATION + category + "/"
        # Get category info
        category_info = self.get_category(category)
        # Set default sort method to category default
        sort_type = category_info['object_sort_method']
        ## Get a list of all objects by looking in the Descriptions directory
        
        try:
            category_objects = []
            for category_object in os.listdir(category_location + "Descriptions"):
                if category_object.split('.')[-1] == "cfg": # Filter the cfg files
                    category_objects.append(category_object[:-4]) # Append it to the list of objects, removing the extension
        except OSError, error_details:
            # If the error number is 13, this is a permission denied error
            if error_details.errno == 13:
                error("""The permissions for the descriptions or it's parent folders prevent pydownloads from accessing it.
                '%s'"""%error_details.strerror)
            # If the error number is 2, this is a path error
            elif error_details.errno == 2:
                error("""There is no descriptions folder for the %s category! Please fix this.
                '%s'"""%(category, error_details.strerror))
            else:
                error("""Something went wrong. Please report this error message to the devs.
                Errno %s: %s"""%(error_details.errno, error_details.strerror))
            user_error("Something went wrong...Please report it!")
        
        ## Sort objects according to the sort_type

        # Sort using the Objects config file that is in the Category folder.        
        
        if sort_type == 0:
            # Get object order from Category.cfg
            ## Category.cfg
        
            """
            ...
            [Objects]
            order: Object1,Object2,Object3
            ...
            """
            # Get the specified order from Category.cfg
            objects_order = category_info['objects_order']
            final_objects = []
            # If the objects_order entry is not empty
            if objects_order != None:
                # Check if objects exist
                for category_object in objects_order:
                    if self.path_exists(category_location + "Descriptions/" + category_object + ".cfg"): # If there is no description file for the object
                        # Add to the final list
                        final_objects.append(category_object)
                    else:
                        # If it doesn't exist, warn
                        WARNINGS.add_warning("There is no cfg file for %s in the %s category! Please fix this if you want to display this object."%(object, category), 1)
                # Then add the rest of the objects to the end of the new list
                for category_object in category_objects:
                    # if they're not already in it
                    if category_object not in final_objects:
                        final_objects.append(category_object)
                # Now return the new objects list
                category_objects = final_objects
        # If it is to be sorted in alphabetical order
        elif sort_type == 1:
            category_objects = [ category_object for category_object in category_objects ]
            # Use the list sort() method
            category_objects.sort()
        # If it is to be sorted in reverse alphabetical order
        elif sort_type == 2:
            category_objects = [ category_object for category_object in category_objects ]
            # Use the list sort() method in reverse
            category_objects.sort(reverse=True)
        # If it is to be sorted by modified time or by its title
        elif sort_type in (3, 4, 5, 6):
            
            def sort_dict(input_dict, reverse=False):
                """Sort dictionary by values and return tuples in a list
                input_dict is the dictionary to be sorted"""
                from operator import itemgetter
                return sorted(input_dict.iteritems(), key=itemgetter(1), reverse=reverse)
            
            # For each object, get the characteristic to be sorted by and put it into a dictionary
            objects_dict = {}
            for category_object in category_objects:
                # If it is to be sorted by modified time
                if sort_type in (3, 4):
                    # Get the description file modification time
                    object_characteristic = os.path.getmtime(LOCATION + category + "/" + "Descriptions/" + category_object + ".cfg")
                # If it is to be sorted by its title
                elif sort_type in (5, 6):
                    # Get its title
                    object_characteristic = self.get_object(category, category_object)['title']
                # Add it to the dictionary as a value
                objects_dict[category_object] = object_characteristic
            # If it is to be sorted with the newest last or in ascending order of its title
            if sort_type in (3, 5):
                objects_list = sort_dict(objects_dict, False)
            # Otherwise, sort with newest first or in descending order of its title
            elif sort_type in (4, 6):
                objects_list = sort_dict(objects_dict, True)
            # We now have a list of the objects sorted by the mtime. We just need to get the objects out of the list
            category_objects = [ category_object[0] for category_object in objects_list ]
            
        # If no sort method has been chosen, just return the objects as they are
        else:
            category_objects = [ category_object[0] for category_object in category_objects ]
        return category_objects

    def get_object(self, category, category_object):
        """This function gets all the information about a particular object, including its title, description and the http locations to its types"""
        category_location = LOCATION + category + "/"
        httpcategory_location = HTTPPATH + category + "/"
        # Open the object config
        object_config = ConfigParser.ConfigParser()
        if self.path_exists(category_location + "Descriptions/" + category_object + ".cfg") == False:
            error("""The config file for %s in the %s category does not exist! Please create a file at <i>%sDescriptions/%s.cfg</i> and add this to it:
<pre>[Object]
title: Title of the object
short_description: A short description of the object
long_description: A long description of the object. To use multiple lines, put a space at the start of every new line

[Custom]
custom_option1: Custom Value
custom_option1_type: string/integer/float/list(comma separated)</pre>"""%(category_object, category, category_location, category_object))
            user_error("%s does not exist in the %s category!"%(category_object, category))
                                
        try:
            object_config.read(category_location + "Descriptions/" + category_object + ".cfg")
        except (ConfigParser.ParsingError,ConfigParser.MissingSectionHeaderError): # If there is an error with the file, call an error
            error("""There was an error parsing %s. Please check through it carefully. It should look like this:
<pre>[Object]
title: Title of the object
short_description: A short description of the object
long_description: A long description of the object. To use multiple lines, put a space at the start of every new line

[Custom]
custom_option1: Custom Value
custom_option1_type: string/integer/float/list(comma separated)</pre>"""%(category_location + "Descriptions/" + category_object + ".cfg"))
            user_error("%s is corrupt. Please report this!"%(category_object))
        try:
            # Get object's title
            object_title = object_config.get('Object', 'title')
        except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
            WARNINGS.add_warning("""There is no title set for %s in the %s category. Please add the following to %sDescritpions/%s.cfg:
<pre>[Object]
title: Title of the object</pre>"""%(category_object, category, category_location, category_object), 1)
            object_title = category_object # Use object's name as its title
        try:
            # Get object's short description
            object_short_description = object_config.get('Object', 'short_description')
        except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
            WARNINGS.add_warning("""There is no short description for %s in the %s category. Please add the following to <i>%sDescriptions/%s.cfg</i>:
<pre>[Object]
short_description: A short description of the object</pre>"""%(category_object, category, category_location, category_object), 2)
            object_short_description = "" # Leave it blank
        try:
            # Get object's long description
            object_long_description = object_config.get('Object', 'long_description')
        except (ConfigParser.NoOptionError, ConfigParser.NoSectionError):
            WARNINGS.add_warning("""There is no long description for %s in the %s category. Please add the following to <i>%sDescriptions/%s.cfg</i>:
<pre>[Object]
long_description: A long description of the object. To use multiple lines, put a space at the start of every new line</pre>"""%(category_object, category, category_location, category_object), 3)
            object_long_description = object_short_description # Default to using the short description
        
        # Get all available types for the object
        category_types = self.get_category_types(category)
        object_types = []
        for category_type in category_types:
            try:
                type_httppath = httpcategory_location + category_type['name'] + "/" + self.get_filename(category_location + category_type['name'], category_object)
                type_loc = category_location + category_type['name'] + "/" + self.get_filename(category_location + category_type['name'], category_object)
                object_types.append((category_type['name'], category_type['title'], type_httppath, type_loc))
            except TypeError: # If the object doesn't have a particular file type, it won't be in that folder
                WARNINGS.add_warning("%s in %s does not have a %s file type!"%(category_object, category, category_type['name']), 3)
        # Get object preview
        if self.get_category(category)['previews'] != "": # Check if the previews variable is blank but present
            try:
                object_preview = httpcategory_location + self.get_category(category)['previews'] + "/" + self.get_filename(category_location + self.get_category(category)['previews'], category_object)
            except TypeError:
                # If there is no preview image, set it to None, and deal with it later
                WARNINGS.add_warning("%s in %s does not have a preview image! A generic one will be used."%(category_object, category), 2)
                object_preview = None
        else:
            object_preview = None
        if self.get_category(category)['thumbnails'] != "": # Check if the thumbnails variable is blank but present
            try:
                object_thumbnail = httpcategory_location + self.get_category(category)['thumbnails'] + self.get_filename(category_location + self.get_category(category)['thumbnails'], category_object)
            except TypeError:
                # If there is no thumbnail, set it to None, and deal with it later
                WARNINGS.add_warning("%s in %s does not have a thumbnail! A generic one will be used."%(category_object, category), 2)
                object_thumbnail = None
        else:
            object_thumbnail = None
            
        # If either the preview or thumbnail is set to None
        if object_preview == None or object_thumbnail == None:
            # Try to use one as a fallback of the other
            if object_preview == None:
                object_preview = object_thumbnail
            else:
                object_thumbnail = object_preview
            # Check if they're now both set to None
            if object_preview == None:
                # If there is no object thumbnail or preview, use the fallback icon....as a fallback
                object_thumbnail = object_preview = self.get_category(category)['object_fallback_icon']
        try:
            # Get custom options
            # Get a list of options in the custom section
            options = object_config.options('Custom')
            # Get the value of each option and put it all into a dictionary
            # {'option':value}
            custom_options = {}
            for option in options:
                custom_option_value = object_config.get('Custom', option)
                # Get option type
                try:
                    option_type = object_config.get('Custom', option + "_type")
                except ConfigParser.NoOptionError:
                    option_type = "string"
                # Option types: string/integer/float/list_string,list_integer,list_float
                if option_type == "integer":
                    custom_option_value = int(custom_option_value)
                elif option_type == "float":
                    custom_option_value = float(custom_option_value)
                # Does it have a \ in it? If so, it is a list and the separator for the list is after the \
                elif "\\" in option_type:
                    # Use the separator to split it into a list
                    option_type = option_type.split('\\')
                    custom_option_value = custom_option_value.split(option_type[1])
                    # Make all the options integers
                    if option_type[0] == "list_integer":
                        custom_option_value = [ int(opt) for opt in custom_option_value ]
                    # Make all the options floats
                    elif option_type[0] == "list_float":
                        custom_option_value = [ float(opt) for opt in custom_option_value ]
                        
                custom_options[option] = custom_option_value
        except ConfigParser.NoSectionError:
            # Ignore the error if there is no custom section but create the dictionary so that there are no further errors
            custom_options = {}
            
        return {'name': category_object,
                'title':object_title,
                'short_description':object_short_description,
                'long_description':object_long_description,
                'types':object_types,
                'preview':object_preview,
                'thumbnail':object_thumbnail,
                'custom_options':custom_options}
    
if __name__ == "__main__":
#    print "Import this file from another script to get access to the FlatFile database class."
    
    print Database().get_category("Documentation")
